/**
 * H5手势密码
 * @param opts {obj}
 *        el {str | body} 需要挂载的节点dom
 *        n {num | 3*3}  手势密码是N宫格 
 *        min_N {num | 4} 设置最小激活点数
 *        callback {fn} 一次绘制完成的回调函数
 */
;
(function (w) {
  var handleLock = function (opts) {
    // 配置项处理
    this.el = opts.el || w.document.body;
    this.n = opts.n || 3; //n * n的九宫格
    this.min_N = opts.n || 4; //最少需要激活的点
    this.dot = []; //所有的点
    this.dotActive = []; //已激活的点
    this.touchFlag = false;
    this.mode = 1; //{num}1:设置密码，2：验证密码，3：校验密码
    this.callback = opts.callback;//回调处理函数
  }
  handleLock.prototype = {
      // 初始化;
      init: function () {
        // 判断是否之前设置过手势密码，设置初始状态
        if (!!(w.localStorage.getItem('HandLockPass'))) {
          this.mode = 3
        }
        this.createCanvas();
        this.drowBase();
        this.createListener();
      },
      // 重置
      reset: function () {
        this.dotActive.splice(0)
        this.dot.splice(0)
        this.drowBase();
      },
      // 创建canvas
      createCanvas: function () {
        var width, elRect;
        elRect = this.el.getBoundingClientRect();
        width = elRect.width < 300 ? 300 : elRect.width;
        var canvas = document.createElement('canvas');
        canvas.width = canvas.height = width;
        this.el.appendChild(canvas);

        var canvas2 = canvas.cloneNode(canvas, true);
        canvas2.style.position = 'absolute';
        canvas2.style.top = '0';
        canvas2.style.left = '0';
        this.el.appendChild(canvas2);

        this.ctx = canvas.getContext('2d');
        this.canvas = canvas;
        this.width = width;

        this.ctx2 = canvas2.getContext('2d');
        this.ctx2.strokeStyle = '#FFF';
        this.ctx2.lineCap = "round";
        this.ctx2.lineWidth = 2;
        this.canvas2 = canvas2;
      },
      // 基建绘制
      drowBase: function () {
        var n = this.n;
        this.r = this.width / ((2 * n + 1) * 2); //动态半径
        this.drowBg();

        for (let i = 0; i < n; i++) {
          for (let j = 0; j < n; j++) {
            // 求出每个点的圆心
            this.dot.push({
              x: (4 * j + 3) * this.r,
              y: (4 * i + 3) * this.r,
              id: i * 3 + j,
            })
          }
        }
        this.drowDot();
      },
      // 背景绘制
      drowBg: function () {
        this.ctx.fillStyle = "#000";
        this.ctx.fillRect(0, 0, this.width, this.width);
      },
      // 空心圆点绘制
      drowDot: function (arr, color) {
        this.ctx.strokeStyle = color || "#FFF";
        var _arr = arr || this.dot;
        _arr.forEach(ele => {
          this.ctx.beginPath();
          this.ctx.lineWidth = 2;
          this.ctx.arc(ele.x, ele.y, this.r, 0, 2 * Math.PI); //{ 圆心水平,圆心竖直，半径，开始处，结束处 }
          this.ctx.stroke();
        });
      },
      // 实心圆绘制
      drowDotActive: function () {
        this.ctx.fillStyle = '#FFF';
        this.dotActive.forEach(ele => {
          this.ctx.beginPath();
          this.ctx.arc(ele.x, ele.y, this.r / 2, 0, Math.PI * 2, true);
          this.ctx.closePath();
          this.ctx.fill();
        });
      },
      // 绘线(移动中的线)
      drowLine: function (p) {
        var len = this.dotActive && this.dotActive.length;
        this.ctx2.clearRect(0, 0, this.width, this.width); // 先清空
        this.ctx2.beginPath();
        this.ctx2.moveTo(this.dotActive[len - 1].x, this.dotActive[len - 1].y);
        this.ctx2.lineTo(p.x, p.y);
        this.ctx2.stroke();
        this.ctx2.closePath();
      },
      // 绘线（点与点之间的线）
      drowLine2Dot: function () {
        var len = this.dotActive && this.dotActive.length;
        if (len >= 2) {
          this.ctx.beginPath();
          this.ctx.moveTo(this.dotActive[len - 2].x, this.dotActive[len - 2].y);
          this.ctx.lineTo(this.dotActive[len - 1].x, this.dotActive[len - 1].y);
          this.ctx.stroke();
          this.ctx.closePath();
        }
      },
      // 事件 监听
      createListener: function () {
        var _this = this;
        this.canvas2.addEventListener('touchstart', function (e) {
          console.log('touchstart');
          var p = _this.getTouchPos(e);
          _this.crashDot(p);
        }, false);
        var t = _this.throttle(function (e) {
          console.log('touchmove')
          var p = _this.getTouchPos(e);
          if (_this.dotActive && _this.dotActive.length) {
            _this.drowLine(p);
          }
          if (_this.touchFlag) {
            _this.update(p)
          } else {
            _this.crashDot(p)
          }
        }, 16, 16)
        this.canvas2.addEventListener('touchmove', t, false);
        this.canvas2.addEventListener('touchend', function (e) {
          console.log('touchend')
          _this.drowOver();
        }, false)
      },
      /**
       * 函数节流
       * 
       * @param func {fn}: 执行方法
       * @param delay {num}: 执行间隔时间，单位毫秒（防抖）
       * @param mustRun {num}: 一定执行时间执行节流
       */
      throttle: function (func, delay, mustRun) {
        var timer, startTime = new Date(),
          self = this;
        return function (e) {
          if (e) {
            e.preventDefault ? e.preventDefault() : null; //提前取消默认事件，不要等到 setTimeout
            e.stopPropagation ? e.stopPropagation() : null;
          }
          var curTime = new Date(),
            args = arguments;
          clearTimeout(timer);
          if (curTime - startTime >= mustRun) {
            startTime = curTime;
            func.apply(self, args);
          } else {
            timer = setTimeout(function () {
              func.apply(self, args);
            }, delay)
          }
        }
      },
      // 更新绘制
      update: function (p) {
        this.drowDotActive();
        this.drowLine2Dot()
        this.touchFlag = false;
      },
      // 绘制完成，状态判断
      drowOver: function () {
        var _this = this;
        this.ctx2.clearRect(0, 0, this.width, this.width); // 清空小尾巴

        if (this.mode === 1) { //设置密码
          if ((this.dotActive && this.dotActive.length) >= this.min_N) {
            this.passWord = [];
            this.dotActive.forEach(ele => {
              this.passWord.push(ele.id);
            });
            this.mode = 2;
            console.log('你再次设置密码')
            this.reset();
          } else {
            console.log('强度不够');
            this.drowDot(this.dotActive, 'red')
            setTimeout(() => {
              _this.reset()
            }, 400);
          }
        } else if (this.mode === 2) { //验证密码
          var flag = true;
          if (this.dotActive.length === this.passWord.length) {
            for (let i = 0, len = this.dotActive.length; i < len; i++) {
              const ele = this.dotActive[i].id;
              if (ele !== this.passWord[i]) {
                flag = false;
                break;
              }
            }
          } else {
            flag = false;
          }
          if (flag) {
            w.localStorage.setItem('HandLockPass', this.passWord.join('-')); // 存储字符串
            _this.reset()
            this.mode = 3;
            console.log('密码设置成功')
          } else {
            console.log('两次密码不一致，请重新设置')
            this.drowDot(this.dotActive, 'red')
            setTimeout(() => {
              _this.reset()
              this.mode = 1;
            }, 400);
          }

        } else { //校验密码
          var flag = true;
          this.passWord = w.localStorage.getItem('HandLockPass').split('-'); // 秘钥
          if (this.dotActive.length === this.passWord.length) {
            console.log(this.passWord)
            for (let i = 0, len = this.dotActive.length; i < len; i++) {
              const ele = this.dotActive[i].id;
              if (ele != this.passWord[i]) {
                flag = false;
                break;
              }
            }
          } else {
            flag = false;
          }
          if (flag) {
            this.drowDot(this.dotActive, 'green')
            setTimeout(() => {
              _this.reset()
              this.mode = 3;
            }, 400);
            console.log('验证成功')
          } else {
            this.drowDot(this.dotActive, 'red')
            setTimeout(() => {
              _this.reset()
              this.mode = 3;
            }, 400);
            console.log('密码错误，请重新输入')
          }
        }
        this.callback && this.callback(this.mode, this.passWord)//回调函数将，当天状态以及密码暴露，以便进行二次处理
      },
      // 判断是否和点碰撞（碰撞检测）
      crashDot: function (p) {
        for (let index = 0, len = this.dot.length; index < len; index++) {
          var ele = this.dot[index];
          var _x, _y, _r;
          _x = Math.abs(p.x - ele.x);
          _y = Math.abs(p.y - ele.y);
          _r = Math.pow(_x, 2) + Math.pow(_y, 2)
          if (_r <= Math.pow(this.r, 2) && !(this.dotActive.includes(ele))) {
            this.dotActive.push(ele);
            this.touchFlag = true
            return;
          }
        }
      },
      // 获得触摸点的相对容器位置
      getTouchPos: function (e) {
        var rect = e.target.getBoundingClientRect();
        var p = { // 相对坐标
          x: e.touches[0].clientX - rect.left,
          y: e.touches[0].clientY - rect.top
        };
        return p;
      },
    },

    w.handleLock = handleLock
})(window)